using System;
using System.Threading;
using System.Drawing;
using System.Collections.Generic;
using Motorway_Simulator.GUI.Designer;
using Motorway_Simulator.GUI;
using Motorway_Simulator.Components;
using System.Runtime.CompilerServices;
using Motorway_Simulator.FileIO;
using Motorway_Simulator.GUI.Simulator;


namespace Motorway_Simulator.Simulator
{
    /// <summary>
    /// Simulation controller object. Controls simulation
    /// </summary>
    public sealed class SimulationController
    {
        public const int ONE_SECOND = 1000;

        public static readonly int RIGHT_HAND_LANE = 2;
        public static readonly int LEFT_HAND_LANE = 0;
        public static readonly int CURRENT_LANE = 2;

        public static readonly int PREVIOUS_ROADUNIT = 2;
        public static readonly int CURRENT_ROADUNIT = 0;
        public static readonly int NEXT_ROADUNIT = 1;
        public bool pause;
        public bool running;
        public const int SPLIT_SECOND = 10;
        
       

        /// <summary>
        /// Driver collection
        /// </summary>
        private SynchronizedCollection<Driver>  driverList;

        private SimObject simObject;
        
        /// <summary>
        /// Constructor to create simulation controller object
        /// </summary>
        /// <param name="roadUnitMap">Road Unit Map</param>
        /// <param name="roadUnits">Road units</param>
        public SimulationController(SimObject sim)
        {
            driverList = new SynchronizedCollection<Driver>();
            simObject = sim;
            
            pause = false;
            running = false;
            Start();
        }

        /// <summary>
        /// Starts the simulation by creating and starting the entrance threads
        /// </summary>
        public void Start()
        {
            Console.WriteLine("Start");
            if (!IsRunning() && !pause)
            {
                Console.WriteLine("Start");
                running = true;
                foreach (RoadUnit roadUnits in simObject.RoadUnits)
                {
                    Console.WriteLine("Start");
                    if (roadUnits is Entrance)
                    {
                        Console.WriteLine("Start");
                        Entrance entrance = (Entrance)roadUnits;
                        entrance.Controller = this;
                        Thread thread = new Thread(new ThreadStart(entrance.Run));
                        thread.Start();
                    }
                }
            }
            else
            {
                Resume();
                pause = false;
            }
            
        }

        /// <summary>
        /// Stop simulation by removing all drivers, stopping all entrances and reseting them
        /// </summary>
        public void Stop()
        {

            if (IsRunning())
            {
                lock(this){
                foreach (Driver driver in driverList)
                {
                    driver.RequestStop();
                }
                driverList.Clear();
                foreach (RoadUnit unit in simObject.RoadUnits)
                {
                    Entrance entrance = null;
                    if (unit is Entrance)
                    {
                        entrance = (Entrance)unit;
                     entrance.StopRequested = true;
                        entrance.Reset();
                    }
                }
                
               
            }

            }

        }

        /// <summary>
        /// Pauses simulation by stoping all drivers and entrances
        /// </summary>
        public void Pause()
        {
            
            if (IsRunning() && !pause)
            {
                pause = true;
                foreach (Driver driver in driverList)
                {

                    driver.RequestStop();
                }
                foreach (Entrance entrance in simObject.RoadUnits)
                {
                    entrance.StopRequested = true;
                    
                }
            }
        }


        /// <summary>
        /// Resume simulation
        /// </summary>
        public void Resume()
        {
            
                foreach (Driver driver in driverList)
                {
                    driver.StartDriving();
                    Thread thread = new Thread(new ThreadStart(driver.Drive));
                    thread.Start();

                }
            
        }

        /// <summary>
        /// Indicate if simulation is running
        /// </summary>
        /// <returns></returns>
        private bool IsRunning()
        {
            return running;
        }

        /// <summary>
        /// Restarts the simulation.
        /// </summary>
        public void RestartSimulation()
        {

            Stop();
            Start();
        }

            /// <summary>
        /// Add vehicle to vehicle data structure
        /// </summary>
        /// <param name="v">Vehicle</param>
        /// 
        [MethodImpl(MethodImplOptions.Synchronized)]
        public void AddDriver(Driver driver)
        {
            driverList.Add(driver);
            Thread thread = new Thread(new ThreadStart(driver.Drive));
            thread.Start();
            
        }


        /// <summary>
        /// Draws all vehicles.
        /// </summary>
        /// <param name="g">The g.</param>
        /// 
       [MethodImpl(MethodImplOptions.Synchronized)]
        public void DrawAllVehicles(Graphics g)

        {
            foreach(GraphicComponent component in driverList)
            {
                component.Draw(g);
                Thread.Sleep(1);
            }

        }

        /// <summary>
        /// Draws the road.
        /// </summary>
        /// <param name="g">The g.</param>
         
        public Bitmap DrawRoad(Graphics g)
        {
             Bitmap bitmap = new Bitmap(3000,3000);
           
             foreach(GraphicComponent component in simObject.RoadUnits)
            {

                Graphics graphics = Graphics.FromImage(bitmap);
                component.Draw(graphics);
                
            }
             return bitmap;
        }

        /// <summary>
        /// Removes the driver.
        /// </summary>
        /// <param name="d">The d.</param>
        /// 
        [MethodImpl(MethodImplOptions.Synchronized)]
        public void RemoveDriver(Driver d)
        {
            d.RequestStop();
            driverList.Remove(d);
        }
         /// <summary>
        /// Gets the neighbours.
        /// </summary>
        /// <param name="vehicle">The vehicle.</param>
        /// <param name="RADIUS">The RADIUS.</param>
        /// <returns>List of all the neighbours within the givin radius of the vehicle </returns>
        [MethodImpl(MethodImplOptions.Synchronized)]
        public List<Vehicle> getNeighbours(Vehicle vehicle, float RADIUS)
        {
            List<Vehicle> closeVehicles = new List<Vehicle>();
            float xPos = vehicle.CurrentPoint.X;
            float yPos = vehicle.CurrentPoint.Y;

            foreach (Driver driver in driverList)
            {
                Vehicle v = driver.Vehicle;
                if (RADIUS < v.Length)
                    RADIUS = v.Length;
                float x = v.CurrentPoint.X;
                float y = v.CurrentPoint.Y;

             
                if (Math.Pow((x - xPos), 2) + Math.Pow((y - yPos), 2) < Math.Pow(RADIUS, 2))
                {
                    
                    if (v != vehicle)
                    {
                        closeVehicles.Add(v);
                    }
                }
            }
            return closeVehicles;
        }

        /// <summary>
        /// Gets the current road unit that the vehicle is currently on.
        /// </summary>
        /// <param name="vehicle">The vehicle.</param>
        /// <returns></returns>
        public RoadUnit getCurrentRoadUnit(Vehicle vehicle)
        {
            float xPos = vehicle.CurrentPoint.X;
            float yPos = vehicle.CurrentPoint.Y;
            RoadUnit currentRoadUnit = null;
            foreach (RoadUnit roadunit in simObject.RoadUnits)
            {
                float x = roadunit.Position.X;
                float y = roadunit.Position.Y;
                if (Math.Pow((x - xPos), 2) + Math.Pow((y - yPos), 2) < Math.Pow(DesignCanvas.squareUnit, 2))
                {
                    currentRoadUnit = roadunit;
                    break;
                }

            }
            return currentRoadUnit;
        }


        /// <summary>
        /// Gets the next road unit.
        /// </summary>
        /// <param name="vehicle">The vehicle.</param>
        /// <returns></returns>
        internal RoadUnit GetNextRoadUnit(Vehicle vehicle)
        {
            return Search(SearchForRoadUnitLane(GetDirectionBoundLanesList(vehicle.CurrentRoadUnit), vehicle.CurrentRoadUnit, CURRENT_LANE), vehicle.CurrentPoint, NEXT_ROADUNIT);
        }


        /// <summary>
        /// Searches the specified linked list for the current road unit.
        /// </summary>
        /// <param name="linkedListToSearch">The linked list to search.</param>
        /// <param name="currentRoadUnit">The current road unit.</param>
        /// <param name="searchType">Type of the search. CURRENT_ROADUNIT = serch for the current roadunit , NEXT_ROADUNIT = search for next road unit , PREVIOUS_ROADUNIT = search for previous</param>
        /// <returns>
        /// Roadunit base on the specified search or null if the road unit was not found
        /// </returns>
        private RoadUnit Search(LinkedList<RoadUnit> linkedListToSearch, Point currentRoadUnit, int searchType)
        {
            LinkedListNode<RoadUnit> roadUnit = linkedListToSearch.First;
            while (roadUnit != null)
            {
                if (roadUnit.Value.Position == currentRoadUnit)
                {
                    if (searchType == 0)
                    {
                        return roadUnit.Value;
                    }
                    if (searchType == 1)
                    {
                        return roadUnit.Next.Value;
                    }
                    if (searchType == 2)
                    {
                        return roadUnit.Previous.Value;
                    }

                }
                roadUnit = roadUnit.Next;
            }
            return null;
        }


        /// <summary>
        /// Searches the specified linked list for the current road unit.
        /// </summary>
        /// <param name="currentRoadUnit">The current road unit.</param>
        /// <param name="side">The side. LEFT_HAND_LANE = gets the left hand side if the road unit ,  RIGHT_HAND_LANE = gets the right hand side of the road unit</param>
        /// <param name="searchType">Type of the search. CURRENT_ROADUNIT = serch for the current roadunit , NEXT_ROADUNIT = search for next road unit , PREVIOUS_ROADUNIT = search for previous</param>
        /// <param name="direction">The direction.</param>
        /// <returns>
        /// The Road unit Beside the road unit given its point
        /// </returns>
        public RoadUnit SearchSide(RoadUnit currentRoadUnit, int side, int searchType, Motorway_Simulator.Components.RoadUnit.Direction direction)
        {
            LinkedList<RoadUnit> lane;
            if (side == LEFT_HAND_LANE)
            {
                lane = SearchForRoadUnitLane(GetDirectionBoundLanesList(currentRoadUnit), currentRoadUnit, LEFT_HAND_LANE);

            }
            else
            {
                lane = SearchForRoadUnitLane(GetDirectionBoundLanesList(currentRoadUnit), currentRoadUnit, RIGHT_HAND_LANE);
            }
            if (lane != null)
            {
                Point pointToSearch = new Point();
                switch (currentRoadUnit.Direction1)
                {

                    case RoadUnit.Direction.NORTH:

                        if (side == LEFT_HAND_LANE)
                        {
                            pointToSearch = new Point((int)(currentRoadUnit.Position.X - currentRoadUnit.ImageWidth), currentRoadUnit.Position.Y);
                        }
                        else
                        {
                            pointToSearch = new Point((int)(currentRoadUnit.Position.X + currentRoadUnit.ImageWidth), currentRoadUnit.Position.Y);
                        }
                        break;

                    case RoadUnit.Direction.EAST:
                        if (side == LEFT_HAND_LANE)
                        {
                            pointToSearch = new Point(currentRoadUnit.Position.X, (int)(currentRoadUnit.Position.Y - currentRoadUnit.ImageHeight));
                        }
                        else
                        {
                            pointToSearch = new Point(currentRoadUnit.Position.X, (int)(currentRoadUnit.Position.Y + currentRoadUnit.ImageHeight));
                        }
                        break;
                    case RoadUnit.Direction.SOUTH:
                        if (side == LEFT_HAND_LANE)
                        {
                            pointToSearch = new Point((int)(currentRoadUnit.Position.X + currentRoadUnit.ImageWidth), currentRoadUnit.Position.Y);
                        }
                        else
                        {
                            pointToSearch = new Point((int)(currentRoadUnit.Position.X - currentRoadUnit.ImageWidth), currentRoadUnit.Position.Y);
                        }
                        break;
                       
                    case RoadUnit.Direction.WEST:
                        if (side == LEFT_HAND_LANE)
                        {
                            pointToSearch = new Point(currentRoadUnit.Position.X, (int)(currentRoadUnit.Position.Y + currentRoadUnit.ImageHeight));
                        }
                        else
                        {
                            pointToSearch = new Point(currentRoadUnit.Position.X, (int)(currentRoadUnit.Position.Y - currentRoadUnit.ImageHeight));
                        }
                        break;
                }
                if (pointToSearch != null)
                {
                    LinkedListNode<RoadUnit> roadUnit = lane.First;
                    while (roadUnit != null)
                    {
                        if (roadUnit.Value.Position == pointToSearch)
                        {
                            if (searchType == CURRENT_ROADUNIT)
                            {
                                return roadUnit.Value;
                            }
                            if (searchType == NEXT_ROADUNIT)
                            {
                                return roadUnit.Next.Value;
                            }
                            if (searchType == PREVIOUS_ROADUNIT)
                            {
                                return roadUnit.Previous.Value;
                            }

                        }
                        roadUnit = roadUnit.Next;
                    }
                }
            }
            return null;
        }
        /// <summary>
        /// Searches for road unit lane.
        /// </summary>
        /// <param name="lanesList">The lanes list.</param>
        /// <param name="currentRoadUnit">The current road unit.</param>
        /// <param name="laneSearchType">Type of the lane search. LEFT_HAND_LANE , RIGHT_HAND_LANE,CURRENT_LANE </param>
        /// <returns></returns>
        private LinkedList<RoadUnit> SearchForRoadUnitLane(LinkedList<LinkedList<RoadUnit>> lanesList, RoadUnit currentRoadUnit, int laneSearchType)
        {
            LinkedListNode<LinkedList<RoadUnit>> lane = lanesList.First;
            while (lane != null)
            {
                if (lanesList.First.Value.First.Value.Direction1 == RoadUnit.Direction.SOUTH ||
                    lanesList.First.Value.First.Value.Direction1 == RoadUnit.Direction.NORTH)
                {
                    if (lane.Value.First.Value.Position.X == currentRoadUnit.Position.X)
                    {
                        if (laneSearchType == CURRENT_LANE)
                        {
                            return lane.Value;
                        }
                        if (laneSearchType == LEFT_HAND_LANE)
                        {
                            return lane.Previous.Value;
                        }
                        if (laneSearchType == RIGHT_HAND_LANE)
                        {
                            return lane.Next.Value;
                        }
                    }
                }
                else if (lanesList.First.Value.First.Value.Direction1 == RoadUnit.Direction.WEST ||
                    lanesList.First.Value.First.Value.Direction1 == RoadUnit.Direction.EAST)
                {
                    if (lane.Value.First.Value.Position.Y == currentRoadUnit.Position.Y)
                    {
                        if (laneSearchType == CURRENT_LANE)
                        {
                            return lane.Value;
                        }
                        if (laneSearchType == LEFT_HAND_LANE)
                        {
                            return lane.Previous.Value;
                        }
                        if (laneSearchType == RIGHT_HAND_LANE)
                        {
                            return lane.Next.Value;
                        }
                    }
                }
            }
            return null;
        }

        /// <summary>
        /// Gets the direction bound lanes list associated with the given road unit.
        /// </summary>
        /// <param name="roadunit">The roadunit.</param>
        /// <returns></returns>
        private LinkedList<LinkedList<RoadUnit>> GetDirectionBoundLanesList(RoadUnit roadunit)
        {
            switch (roadunit.Direction1)
            {
                case RoadUnit.Direction.NORTH:
                    return simObject.NorthBoundLanes;

                case RoadUnit.Direction.SOUTH:
                    return simObject.SouthBoundLanes;

                case RoadUnit.Direction.EAST:
                    return simObject.EastBoundLanes;

                case RoadUnit.Direction.WEST:
                    return simObject.WestBoundLanes;

                default: return null;

            }


        }
        
    }



    }


